Refactoring logic outside of Context.Consumer - javascript

To get my desired behavior I ended up nesting ternarys inside of my Context.Consumer and it looks pretty ugly.
I have tried refactoring out some of the logic, but I have been unable to get rerenders to trigger when the props/state change unless I use Context.Consumer.
Working on an app for a project. It uses SWAPI to search for pretty much anything in the Star Wars universe. I have a that changes depending on the user's input, and then loads the results of their search.
I started out just trying to get the results to render after the fetch finished and was having some issues of handling the props/state update of the component not triggering a re-render. We recently learned about React's Context api so I decided to try using that in this simple project even though it's 100% not necessary. I did some reading and ended up with the Context.Consumer. It works great for what I want, and triggers re-renders when I need it to.
render() {
return (
<AppContext.Consumer>
// value here is: characterData: this.state.characterData from App.js
// characterData is in App.js' state and is updated after the fetch is successful.
{value => (
<section className="results-container" style={this.styles}>
{/* FIX refactor this \/ */}
{/*
If they haven't searched for anything, characterData doesn't exist yet,
well it's an empty {object} so it renders the 'Enter a name and hit submit!'
If they search and the results are empty, it tells them to try another name.
If the search works it renders the results.
*/}
{value.characterData.results ? (
value.characterData.results.length > 0 ? (
value.characterData.results.map(item => (
<p style={this.styles} key={item.url} className={item.index}>
{item.name ? item.name : item.title}
</p>
))
) : (
<span>Coulnd't find anything! Try another name or topic.</span>
)
) : (
<span className="default-results-text">
Enter a name and hit submit!
</span>
)}
{/* FIX refactor this /\ */}
</section>
)}
</AppContext.Consumer>
);
}
I am not getting any errors, just trying to figure out a way to clean this up.
When trying to move logic outside the Context.Consumer I have to fall back to using state/props and then it never rerenders.
I've tried using componentWillReceiveProps() and static getDerivedStateFromProps() but again couldn't get rerenders to trigger.
Hosted on Zeit: https://starwars-search-nm7mk0268.now.sh/

I ended up refactoring all of the logic to the parent component and just returning the value of all the logic. This allowed me to get away from nested ternarys and clean up this component's render method

Related

useState element not rerendering when triggered by an event handler in a child component

I have a parent component with a useState that is being displayed. What I want to do is have a child component be able to update this state to change what the parent is displaying. I currently have the following:
function Parent() {
const [myWindow, setMyWindow] = useState(null)
useEffect(() => {
setMyWindow(<Child updateWindowFunc={() => setMyWindow(someNewWindow)} />)
}, []}
return (
<div>
{myWindow}
</div>
)
}
function Child({updateWindowFunc}) {
//Somehow calls updateWindowFunc
}
(Note that my actual code is set up somewhat differently, so don't mind the syntax as much as the general concept.)
I've found that I can get the value of myWindow to change, but the actual display doesn't show any change. I've tried forcing a re-render after the change, and adding a useRef to display useRef.current, but nothing seems to update which window is actually being rendered.
What am I doing wrong?
Edit:
I've found that it works if I switch to a different type of component, but if its just a different element of the same component then there is no re-render. I've been using React.createElement(), so I would think the 'objects' are distinct, but maybe I just misunderstand how this works.
Can you provide more details or working\not working code example that has more info?
Currently it's hard to get, cause that doesn't make any sense since you can just render it like below and that code will have same result as one that you are trying to run
function Parent() {
return (
<div>
<Child />
</div>
)
}
So for better understanding and fixing a problem \ finding a different approach, please, provide more details

Do React console warnings need to be dealt with before deploying?

I've been learning to code for about 3 months now and I've gotten pretty far with an APP I created but when I inspect the page, I have over 50 warnings. Here are some of the warnings
react-dom.development.js:67 Warning: Text content did not match. Server: "Sign out" Client: "Create"
at a
at O (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1647879270456:148896:19599)
at Link (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1647879270456:89219:19)
THERE ARE BOUT 10 LINKS SIMILAR TO THE ONES ABOVE THAT FOLLOW
VM2101 react_devtools_backend.js:3973 Warning: Each child in a list
should have a unique "key" prop.
Check the render method of AllAssets. See
https://reactjs.org/link/warning-keys for more information.
at O (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1647879270456:148896:19599)
at AllAssets (http://localhost:3000/_next/static/chunks/pages/index.js?ts=1647879270456:40:73)
THERE ARE BOUT 10 LINKS SIMILAR TO THE ONES ABOVE THAT FOLLOW
VM2101 react_devtools_backend.js:3973 Warning: The tag is
unrecognized in this browser. If you meant to render a React
component, start its name with an uppercase letter.
at image
at O (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1647879270456:148896:19599)
at span
at O (http://localhost:3000/_next/static/chunks/pages/_app.js?ts=1647879270456:148896:19599)
THERE ARE BOUT 10 LINKS SIMILAR TO THE ONES ABOVE THAT FOLLOW
These equal out to about 100 lines of errors all seeming to be referring to the same issues. To be honest, I don't really know how to address tese. Like for example, the one that sasys that all child lists should have a key prop. I'm pretty sure it's referring to some instances where I have the following:
return (
<ul>
{assets.map((result) => {
const {
collection: { name },
data: { image },
} = result;
if (name !== null) {
return (
<Container>
<Card key={name}>
<CollectionAvatar
image={image}
/*name={name || collection}*/
type="collection"
/>
<br></br>
<br></br>
{name}
</Card>
</Container>
);
}
})}
</ul>
);
}
You see, I added a key, but I don't really know if that's the right way to do it or if I added the wrong key etc. I'm still learning about this.
Typing this up I realize that I have a few questions:
Are the errors that I have shown critical? Do they need to be fixed before deploying?
Is there anyway to hide these in the meantime if they are not critical so that I can fix them at a later time?
How can I find out what the error is talking about. I see a link but I don't understand what the result is showing me.
Thanks for you patience. Like I said, I'm learning and I realize I still have a lot to learn but I'd appreciate some clarity on this!
Well, you could deploy your app with warnings. But you should keep in mind that they could lead to unpredictable behaviors in the future, so address them as soon as possible. If I remember correctly, they will still show up the console, but you will be able to deploy the app. It's also worth remembering that some CI servers (like netlify) don't allow you to deploy your app with warnings, so you need to set an environment variable before deploying. (CI=false)
It's hard to know exactly what is the problem in your code just by looking at the logs. But to solve the one related to the keys, you need to pass a unique key as props to the component that is being returned by the map function. So in your case, instead of passing key={name} to the Card component, try passing it to the Container component.

React Router: Component is not rendering but the URL is updated

This kind of "component not rendering in React Router" type of questions seem to be a very frequently asked question. I have looked through everything but I could not find a solution for my problem.
Here is how my code look like:
render(){
return(
<div>
<SearchBar searchBody={this.state.body}/>
<Route path = "/ranked/" component ={Ranked}></Route>
</div>
);
}
Above, the component Ranked is created which, depending on the subpath renders different things.
(For example, localhost:3000/ranked/NBA)
function SearchDropDown(props){
return(
<div className = "searchDropDownItem">
<Link to={"/ranked/"+props.item.url}>{props.item.name}</Link>
</div>
)
}
Above is a different component with the Link tag, which, depending on the url, links to different subpath of /ranked/.
The problem is that let say I am on localhost:3000/ranked/NBA.
If I get redirected to localhost:3000/ranked/WNBA through the linked tag, the url is updated correctly but the component is refreshed to itself.
From the solutions from previous related posts, I have tried
<Route exact path = "/ranked" ...
But it didn't work.
What could be the problem here? How do I solve it?
I would recommend your route look something like this instead /route/:org if you expect to receive props at the end of that specified route. Then inside your Ranked component you would use this.props.match.params.org to get the organization you want ie. (NBA, WNBA). After you have received these props in your Ranked component then you can render what ever you need for that specified organization. Hopefully this makes some sense.

Input fields lose focus on each value change in a complex Formik form

I'm building an app that will need a complex form generation (from json/js object).
Here's an example of what I'm building: https://codesandbox.io/s/formik-form-wizard-test-vcj1t
My problem is the following:
all the input fields lose focus on every value change
The folder structure of the CodeSandbox project is the following (for easier code comprehension):
./src:
App.js - main file with all the form generation (generateWizardSteps is the main function that does all the generation).
formSetup.js - that's an object that defined form configuration. From objects like this I need to build dynamic forms. This object has the array of pages (these are the steps of the wizard), and each page has an array of fields (like input, select, checkbox etc). In its turn, each field has props property. props is what I pass to the React component as props.
formComponents.js - this file contains all the form field React components that I use for generating my forms.
decorateWithFormik.js - this file is just to make App.js a bit smaller. It's just the useFormik decorator.
The form is built using the formik library. Also, as I need a wizard-like form, I've found a nice library for it: formik-wizard-form.
I've looked through the stackoverflow questions on similar topics but couldn't find something that could fit my needs. Many questions/answers are about the problem with dynamic key props, but mine are static as far as I can tell (they all are taken from the initial formSetup object).
Another thing that I've noticed is that my form gets re-rendered on every value change, but I'm not sure if this is a problem at all.
Could you help me to figure out what the problem is, why does it happend and how to make my form fields not lose focus?
Solved the issue
I've removed all the possible component creation code and moved everything inside the component props of the Step component provided by the formik-wizard-form library.
Here's my working solution: https://codesandbox.io/s/formik-form-wizard-test-lz3x7 (hope this will help somebody)
Many thanks to everyone in the comments section!
Main insights:
Losing focus means that the component unmounts and remounts every time.
This unmounting/remounting behaviour on every change was caused by the creation of components inside the render function of another component. Consequently, on every re-render the input components were created anew.
My final working code:
const MyForm = props => {
return (
<FormikWizardProvider {...props}>
{renderProps => (
<Wizard {...renderProps}>
<StepsList>
{formSetup.pages.map(page => (
<Step
title={page.name}
key={page.name}
component={() =>
page.fields.map(field => {
if (
field.props &&
field.props.visibilityCheckbox &&
!props.values[field.props.visibilityCheckbox]
) {
return null;
}
const fieldProps = {
formik: props,
key: field.props.name,
...field.props
};
switch (field.type) {
case "input":
return <MyTextInput {...fieldProps} />;
case "radio":
return <RadioButtonGroup {...fieldProps} />;
case "checkbox":
return <MyCheckbox {...fieldProps} />;
case "select":
return <MySelect {...fieldProps} />;
default:
return null;
}
})
}
/>
))}
</StepsList>
<ButtonsList>
<PreviousButton />
<NextButton />
<SubmitButton />
</ButtonsList>
</Wizard>
)}
</FormikWizardProvider>
);
};
export default decorateWizardWithFormik(MyForm);

UI does not render after state is changed

I have an app (project in Udacity) in React which display books on my shelves according to categories: Currently Reading, Want to Read and Read. Every time I change the category say from Want to Read to Currently Reading the book will move to the right category and in this case it would be Currently Reading. My code works on this one with no problem. However, you can also search from the vast library of books wherein you could move to your shelf, by default the category is None, although you could include the existing books in your shelf as part of being search (aside from the main library of books). Now, my problem is this, if I move from None to Want To Read category for example my UI does not change after I click the back button that brought me back to the main page (i.e. App.js). When I do however, change of category in the main, I have no problem. Also my function for updating the Book Shelf in App.js when called does not show any error in the console.
I have the following components:
App
|
|--BooksSearch
|--BooksList
| |--BookShelf
|--BooksSearchPage
|--BookShelf
The BooksList and BooksSearch displays the books and the search button respectively in the main page (i.e. App.js). The BooksSearchPage allows user to search books from the library to move into the shelves. The BookShelf displays the list of books whether they are in the shelves or in the library.
This is my App.js
class App extends React.Component {
state = {
mybooks : [],
showSearchPage: false
}
componentDidMount() {
BooksAPI.getAll().then( (mybooks)=> {
this.setState({mybooks})
})}
toCamelShelf(Shelf) {
if (Shelf==="currentlyreading") return "currentlyReading"
if (Shelf==="wanttoread") return "wantToRead"
return Shelf
}
updateBookShelf = (mybook, shelf) => {
shelf=this.toCamelShelf(shelf)
BooksAPI.update(mybook, shelf).then(
this.setState((state)=>({
mybooks: state.mybooks.map((bk)=>bk.id === mybook.id ?
{...bk, shelf:shelf} : bk)
})))}
render() {
return (
<div className="app">
{this.state.showSearchPage ? (
<Route path='/search' render={({history})=>(
<BooksSearchPage mybooks={this.state.mybooks} onSetSearchPage={
()=>{ this.setState({showSearchPage:false});
history.push("/");
}}
onUpdateBookShelf={this.updateBookShelf}
/>
)} />
) : (
<Route exact path='/' render={()=>(
<div className="list-books">
<div className="list-books-title">
<h1>My Reads</h1>
</div>
<BooksList mybooks={this.state.mybooks}
onUpdateBookShelf={this.updateBookShelf}/>
<BooksSearch onSetSearchPage={()=>this.setState({showSearchPage:true})}/>
</div>
)} />
)}
</div>
)
}
}
export default App
And since the code is too long, I included my repo in Github. I am very new to ReactJS and have been debugging this problem for the last 3 days but to no avail.
I'm having a hard time understanding the app enough to know why exactly, but it sounds like its a state issue.
If you navigate away and come back, or click something and it doesn't update properly, the state isn't being updated at that moment (that event) or the state wasn't saved correctly right before that event.
As soon as you reproduce the problem event, ask yourself "what was the state right before I did this?" and "why is the state how it is now?"
Did you forget to update the state?
Is it getting the wrong state from somewhere?
Did you call this.setState({ something })?
Did you overwrite the state instead of adding to it?
Is there a missing state update?
On both pages, right before and right after, add in the render method: console.log(this.state) and if needed, console.log(this.props). I think you will see the problem if you look there. The question is how exactly did it get like that? Re-visit all your state updates.
If you navigate away and come back, where does it get that state from? Why is that data in there?
Remember, React is a state machine. State is an object that has a snapshot of data every time you look at it. It's like looking at a piece of paper with all your data on it. If you leave the room and come back and the data isn't there, what updated your state and made it go away? or why didn't it get added to your state? That mechanism there is causing your problem.
I see a few spots in your code to focus on:
BooksAPI.update(mybook, shelf).then(
this.setState((state)=>({
mybooks: state.mybooks.map((bk)=>bk.id === mybook.id ?
{...bk, shelf:shelf} : bk)
})))}
and
<BooksSearchPage mybooks={this.state.mybooks} onSetSearchPage={
()=>{ this.setState({showSearchPage:false});
history.push("/");
}}
onUpdateBookShelf={this.updateBookShelf}
and
<BooksList mybooks={this.state.mybooks}
onUpdateBookShelf={this.updateBookShelf}/>
<BooksSearch onSetSearchPage={()=>this.setState({showSearchPage:true})}/>
also right up here:
class App extends React.Component {
state = {
mybooks : [],
showSearchPage: false
}
componentDidMount() {
BooksAPI.getAll().then( (mybooks)=> {
this.setState({mybooks})
})}
One of them is acting too strongly or one of them isn't updating at the right time, or data is getting overwritten, I suspect.
The console.log() should be most helpful. If your data is missing. Make it show up there at that time and the problem will go away :) (P.S. that setState on componentDidMount looks a little suspect).

Categories

Resources