How to migrate from MVC to React
In this How-To, we are using the same Sample MVC website as we used in our Jamstack for Sitecore MVC Tutorial.
There are several steps needed to start rewriting components in React.
#
Before you startWe are going to rewrite ImageTextBlock
component from the front page of the Uniform MVC sample app. First, let's save outer html from inspect element in notepad. We are going to use it later.
This is the html in case if you've missed this step for some reason:
<div class="row pb-5"> <div class="col-md-8"><img src="https://sc93.sc/-/media/project/uniform-mvc-kit/waves.ashx?h=480&iar=0&w=720&hash=15DB7893AEEA90AE6394DD0F9B21F985" class="card-img-top img-fluid" alt="waves" width="720" height="480"></div> <div class="col-md-4"> <h3 class="my-3">Project Description</h3> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam viverra euismod odio, gravida pellentesque urna varius vitae. Sed dui lorem, adipiscing in adipiscing et, interdum nec metus. Mauris ultricies, justo eu convallis placerat, felis enim.</p> <h3 class="my-3">Project Details</h3> <ul> <li>Lorem Ipsum</li> <li>Dolor Sit Amet</li> <li>Consectetur</li> <li>Adipiscing Elit</li> </ul> </div></div>
#
Step 2. Create a simple component in Uniform Next.js AppIn
.env
file in the root, setUNIFORM_OPTIONS_MVC_MODE=mixed
This setting is used in Uniform Service to understand, which data to request from Sitecore for rendering. Without mixed mode, only
map.json
andpage.json
services are requested. With mixed mode set, for each component we'll also requestitem.json
, which allows us to rewrite components in React, and get current datasource item data throughprops
Create
components
folder in the root:In
components
folder, create ImageWithText.jsx file, and fill it with the next contents:export const ImageWithText = (props) => { return ( <div>I'm a new ImageWithText component!</div> )}
Now we need to tell our app where the new component is. Find
pages/[[..slug]].tsx
file:Add the next lines right after defining of componentsIndex const:
import { ImageWithText } from "../components/ImageWithText";componentsIndex.ImageTextBlock = ImageWithText;
This is how your slug.tsx is going to look like now:
Now, try to run
yarn dev
and loadlocalhost:3000
. This is what you should see:Note that the MVC component has been replaced with our new simplistic React component.
Congrats! Now you've rewritten your MVC component in React!
Now, let's try to make this component to look exactly like MVC component we had before.
#
Step 3. Make the new React component look like beforeTake the html code from step 1, and run it through any html to jss converter - there are several of them available as extensions in Visual Studio Code, for example. The resulting code should look like this:
<div className="row pb-5"> <div className="col-md-8"> <img src="https://sc93.sc/-/media/project/uniform-mvc-kit/waves.ashx?h=480&iar=0&w=720&hash=15DB7893AEEA90AE6394DD0F9B21F985" className="card-img-top img-fluid" alt="waves" width={720} height={480} /> </div> <div className="col-md-4"> <h3 className="my-3">Project Description</h3> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam viverra euismod odio, gravida pellentesque urna varius vitae. Sed dui lorem, adipiscing in adipiscing et, interdum nec metus. Mauris ultricies, justo eu convallis placerat, felis enim.</p> <h3 className="my-3">Project Details</h3> <ul> <li>Lorem Ipsum</li> <li>Dolor Sit Amet</li> <li>Consectetur</li> <li>Adipiscing Elit</li> </ul> </div> </div>
Replace our
<div>I'm a new ImageWithText component!</div>
inImageWithText.jsx
file with this code. NowImageWithText.jsx
should look like this:export const ImageWithText = (props) => { return ( <div className="row pb-5"> <div className="col-md-8"> <img src="https://sc93.sc/-/media/project/uniform-mvc-kit/waves.ashx?h=480&iar=0&w=720&hash=15DB7893AEEA90AE6394DD0F9B21F985" className="card-img-top img-fluid" alt="waves" width={720} height={480} /> </div> <div className="col-md-4"> <h3 className="my-3">Project Description</h3> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam viverra euismod odio, gravida pellentesque urna varius vitae. Sed dui lorem, adipiscing in adipiscing et, interdum nec metus. Mauris ultricies, justo eu convallis placerat, felis enim.</p> <h3 className="my-3">Project Details</h3> <ul> <li>Lorem Ipsum</li> <li>Dolor Sit Amet</li> <li>Consectetur</li> <li>Adipiscing Elit</li> </ul> </div> </div> )}
Switch over to your browser. If you are still running
yarn dev
, then your content on localhost:3000 should already be replaced and our component should look exactly like when it was rendered through MVC:
Congrats! now our new React component looks exactly like the former MVC component. We are getting closer and closer to the fully React presentation!
#
Step 4. Pass and use props in a new React componentHowever, as you might have noticed, our new component now contains only hardcoded texts and images. How do we make it to represent Sitecore fields?
#
Step 4.1 Sitecore Configuration for getting rich component propsOur new component props contain an object called renderingContext, that is being formed from page.json
returned by Uniform Connector. So to tell our page.json
to contain specific datasource information, needed for ImageWithText component, we need somehow to tell our Sitecore side that this particular rendering will need to pass data for it's datasources.
This can be done in two ways. For both of them we need to know our rendering id first. Maybe you already know which rendering it is - so just save its id somewhere. If you can't pinpoint the exact rendering corresponding to the part of the page you want to rewrite, you can look into page.json API output and look for the content from that place on the page:
ef4ad74fe1ac443288ced69aaea2bec0
is the Id of the actual Sitecore rendering
Now, when you know ID of the rendering, either use a config setting to override it (Option 1) or change rendering's item template (Option 2). If you use a config setting, you will still be able to use MVC rendering on your Sitecore side - for example, for Experience Editor. When you change template of the rendering, you won't see this rendering in Experience Editor anymore.
#
Option 1. Adding a config setting1) Activate uniform-mvc-kit.Uniform.Deployment.ReactRenderings.config
in App_Config\Include\zzz_uniform-mvc-kit
by removing .disabled
extension from it.
2) Instead of sampleRenderingId being overridden, add our rendering id, so that your config file now looks like this:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/"> <sitecore> <uniform> <services> <renderingNodeService> <javaScriptRenderingOverrides> <ImageTextBlock>{EF4AD74F-E1AC-4432-88CE-D69AAEA2BEC0}</ImageTextBlock> </javaScriptRenderingOverrides> </renderingNodeService> </services> </uniform> </sitecore></configuration>
#
Option 2. Changing template of the rendering1) Find rendering definition item by rendering id in Sitecore:
2) Change template Change template of the rendering we are planning to rewrite to Uniform Javascript Rendering
:
You are going to get warning about fields that will miss after change - just ignore it.
After template change your rendering item will look like this:
Important: now you need to still have your Build service up and running with yarn start
or yarn dev
. We need to keep it running now, because we will need to publish this rendering item.
4) Publish rendering item.
#
Step 4.2 Getting data in the componentSince UFS 7.2 it is available to get data in a component in two ways:
- Component Model
- Rendering Context [Deprecated]
#
Configure the component modelTo configure the component model need to set the rendering id for the pageModelService
and register a model builder for the rendering datasource in the componentModelBuilderService
.
The model builder should have componentId and parameterName attributes:
- componentId is a rendering ID or camel-cased rendering name. Example: rendering name
Image Text Block
->imageTextBlock
- parameterName is a component property name. Use
datasource
when you need to build model from rendering datasource or page item, if datasource is not specified, or use camel-cased parameter name for item-based rendering parameters. (e.g. Media Item converts to mediaItem)
<configuration xmlns:set="http://www.sitecore.net/xmlconfig/set/" xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore> <uniform> <siteConfigurations> <siteConfiguration name="uniform-mvc-kit"> <componentModel> <pageModelService ref="uniform/services/pageModelService"> <renderings hint="list:RegisterRenderingId"> <ImageTextBlock>{EF4AD74F-E1AC-4432-88CE-D69AAEA2BEC0}</ImageTextBlock> </renderings> </pageModelService> <componentModelBuilderService ref="uniform/services/componentModelBuilderService"> <modelBuilders hint="raw:RegisterModelBuilder"> <imageTextBlock componentId="{EF4AD74F-E1AC-4432-88CE-D69AAEA2BEC0}" parameterName="datasource" type="Uniform.ModelBuilders.ItemFieldsModelBuilder, Uniform.ComponentModel"> <Templates hint="list:AddTemplate"> <ImageTextBlock>{B1A05AE5-6854-4358-B336-316A514C592F}</ImageTextBlock> </Templates> <Fields hint="raw:AddField"> <field>Text</field> <field>Image</field> </Fields> </imageTextBlock> </modelBuilders> </componentModelBuilderService> </componentModel> </siteConfiguration> </siteConfigurations> </uniform> </sitecore></configuration>
To check that model configured visit page.json and find models
, it contains the model for overridden rendering:
info
Detailed documentation on how to use Model Builders
#
Getting the component model in the componentWhen the component model configurations are done, we can use the model as a part of props. To finishe the compoentn migration process we need to update the ImageWithText.jsx. Here is the final code for ImageWithText:
export const ImageWithText = ({datasource}) => { return ( <div className="row pb-5"> <div className="col-md-8"> <img src={datasource.image.url} className="card-img-top img-fluid" alt="waves" width={720} height={480} /> </div> <div className="col-md-4"> <div dangerouslySetInnerHTML={{ __html: datasource.text }} /> </div> </div> )}
info
Although we need to use dangerouslySetInnerHtml for Rich Text fields, otherwise you'll get all the <h3>
, <ul>
and <li>
tags from Rich Text as is on the page.
Congrats! now you've successfully overridden MVC component with React representation, followed the same style and retrieved field values from rendering's datasource!
#
Getting datasource context data in the component [Deprecated since UFS 7.2]info
To enable the feature use Uniform.Data.Page.LegacyReactDatasources.Enabled
setting
To check that step #4.1 is done correctly visit page.json and find datasources
, it contains the datasource for overridden rendering:
With the change above, fields from datasource item, specified for the rendering, will now be propagated to props of the component that we are rewriting. More specifically, datasource became a part of the renderingContext
object in props, and can be accessed now via props.renderingContext.item.fields
in that component. Let's check it!
- If you try to read
renderingContext
object fromprops
inImageWithText
component, you should get everything that is available on the page at the moment:
export const ImageWithText = (props) => { const { renderingContext } = props; console.log({ renderingContext });}
- Check your console in browser where you have localhost:3000 running and observe the renderingContext:
Now we can see our datasource item fields being passed as a part of props!
This is the point where our component becomes fully functional. The only thing we need to do now is to replace hardcoded parts of the ImageWithText.jsx with the data available in props:
- our image source will become
{renderingContext.item.fields.image.url}
- our text will become
{renderingContext.item.fields.text}
- our image source will become
Although we need to use dangerouslySetInnerHtml for Rich Text fields, otherwise you'll get all the <h3>
, <ul>
and <li>
tags from Rich Text as is on the page.
Here is the final code for ImageWithText:
export const ImageWithText = (props) => { const { renderingContext } = props; const { item } = renderingContext; const { fields } = item; return ( <div className="row pb-5"> <div className="col-md-8"> <img src={fields.image.url} className="card-img-top img-fluid" alt="waves" width={720} height={480} /> </div> <div className="col-md-4"> <div dangerouslySetInnerHTML={{ __html: fields.text }} /> </div> </div> )}