Reactjs not updating dom classes after splicing state - reactjs

Hello I am new to react and have a question with changing classes and animation onClick.
I am trying to move up and down only with css classes that I add or remove to an array that is in my className.
app.js i have this in state
updown: ["containerMG"],
and here is how i render my components in app.js
render() {
return (
<div>
<div className="movies-container">
{this.state.filmovi.map((film, index) => {
return <Film
naslov={film.naslov}
naslovnaSlika={film.naslovnaSlika}
key={film.id}
openFilm={() => this.injectFilm(index)}/>
})}
</div>
<Gallery />
<ContainerMG
selectedFilm={this.state.selectedFilm}
klasa={this.state.updown}
zatvori={this.closePreview}/>
</div>
);
}
my component looks like this
const ContainerMG = (props) => {
return (
<div className={props.klasa.join(' ')}>
<img onClick={props.zatvori} src="xxx" alt="close" className="close-popup" />
<p>{props.selectedFilm.naslovFilma}</p>
</div>
)
}
this is how the div moves up
injectFilm = (filmIndex) => {
this.state.updown.push("position-top");
const selectedFilm = this.state.filmovi.find((film, index) => index === filmIndex)
this.setState((prevState) => ({
selectedFilm
}))
}
this is how i tried to move it down
closePreview = () => {
this.state.updown.splice(1);
}
i am pretty sure i should not change the state directly, and also when i change it to remove the "position-top" class the dom doesn't reload.
thank you for all the help in advance and if i didn't show something that you need please do write.

You're right, you should never change the state directly like that, but rather, use a setState() method. Doing this.state.updown.push("position-top"); will mutate it. Let's say you want to remove whatever is last from the array, you could do something like:
const {updown} = this.state;
updown.pop();
this.setState({updown: updown});
Which would cause a re-render. Treat the state as if it were immutable.

Related

How to render based on a condition in Typescript?

Say we have a component, with the following definition:
export const MentForm: React.FC<IProps> = (props: IProps) => {
It's return is a div with a load of HTML, some of which is the following:
<div className="form">
<MentFormButton {...fProps} />
<FlyComp
user={props.user}
onClick={props.onShowFly(props.user)}
/>
{showFlyForm (
<FlyForm
onFlyed={() => {
if (props.onFlyClicked) {
props.onFlyClicked(flies)
}
setShowFlyForm(false)
}}
onFlyFail={() => {
setShowFlyForm(false)
}}
user={flies}
/>
)}
</div>
Now, when we click the button, onShowFly gets triggered
We have another TSX file that, within a Test functional component, there is a return with a <div> and that has the following:
<MentForm
user={new User}
onCancelClick={() => {
if (props.user) {
wait()
}
}}
onShowFly={() => {// HERE //}}
/>
Where I've wrote "HERE", I want to be able to have the stuff in curly braces in the MentForm component to activate.... but I don't know how to do this....
Within the component that has your showFlyForm, you can use useEffect to fire off a function whenever the state changes and then just check for showFlyForm === true or whichever logic you use to check for showing the form.
Ex:
useEffect(() => {
showFlyForm && [your HERE function]
}, [showFlyForm])

How to create a div on click of a button in react?

I've just started learning React. I'm working on a project for practice. It has two different components Nav & Main.I'm calling these components in App.js. My Nav component has a number of coloured divs which show up on clicking the plus icon. Now, I want to create a new div in Main.js on click of a coloured div in Nav.js. This new div should have the same background colour as the one we click on. I'm confused how to do it. Please! Help me out.
This is my Nav component.
const Nav = () => {
const [showColors, setShowColors] = useState(false);
return(
<div className="NavBar">
<h1 className="NavHeading">Notes.</h1>
<div onClick={() => setShowColors(!showColors)} className="PlusImg"></div>
<div className={showColors ? "" : "ColorBar"}>
<div className="Color Color1"></div>
<div className="Color Color2"></div>
<div className="Color Color3"></div>
<div className="Color Color4"></div>
<div className="Color Color5"></div>
</div>
</div>
);
};
export default Nav;
This is my Main component.
const Main = () => {
return(
<div className="Main">
<div className="NoteItem"></div>
</div>
);
};
export default Main;
I want to create new div with class name of NoteItem as the one written in the Main component.
I'm not 100% sure I understand what you're trying to do. Apologies if I've misunderstood.
I'd approach this by keeping track of the items that have been added in component state:
const App = () => {
// starting with an empty array
const [items, setItems] = React.useState([]);
And defining a handler for adding an item:
const App = () => {
const [items, setItems] = React.useState([]);
// itemClass is just a string, e.g. "color1" or "color5"
const addItem = itemClass => setItems([...items, itemClass]);
The spread syntax used above creates a new array containing the previous array plus the new item:
const oldArr = ['color1','color2'];
const newArr = [...oldArr, 'color3']
// newArr is now ['color1', 'color2', 'color3'];
You can then pass the items to Main as a prop, which can render a div for each item:
const App = () => {
const [items, setItems] = React.useState([]);
const addItem = itemClass => setItems([...items, itemClass]);
return (
<Main items={items} /> {/* pass items array to Main */}
);
}
const Main = ({items}) => { // {items} is the equivalent of props.items
// render a div for each item in the items array
return items.map( item => (
<div className={item}>This item has a class of {item}</div>
)
}
And pass the addItem handler to Nav so it can tell App to add the clicked item:
const Nav = ({addItem}) => {
return (
<div className={showColors ? "" : "ColorBar"}>
<div onClick={() => addItem('Color1')} className="Color Color1"></div>
{/* repeat for Color2, etc. */}
</div>
);
}
With this place, when a Nav div is clicked an item will get added to App's items array, which will trigger a re-render, passing the updated array to Main, and you'll see the new div.
a bit of housekeeping:
In the Main component above you're going to get a react key warning. I omitted the key in the interest of readability, but you'll need to include a key prop that's unique for each item. The easiest way to do this is to just use the index of the iteration:
const Main = ({items}) => {
// add key={index} to make react happy
return items.map((item, index) => (
<div key={index} className={item}>This item has a class of {item}</div>
)
}
Also, the items.map call will blow up if items isn't provided. You can dodge this by setting it to an empty array by default:
const Main = ({items = []}) => {
// ...
}
First of all you need to define your state in your App component and pass it to your components as an argument because both your Nav and Main components needs to access your state. You can do it like this
class App extends React.Component{
constructor(){
super()
this.state = {
showColors: false
}
}
}
After that you can take elements color with the onClick event listener. Define it in your state as well. And create a div element inside your Main component with it.
Dont forget to pass it as an argument to your Main component too.

React wrapper component (HOC) does not re-render child component when it's props are changing

My wrapper component has this signature
const withReplacement = <P extends object>(Component: React.ComponentType<P>) =>
(props: P & WithReplacementProps) => {...}
Btw, full example is here https://codepen.io/xitroff/pen/BaKQNed
It's getting original content from argument component's props
interface WithReplacementProps {
getContent(): string;
}
and then call setContent function on button click.
const { getContent, ...rest } = props;
const [ content, setContent ] = useState(getContent());
I expect that content will be replaced everywhere (1st and 2nd section below).
Here's the part of render function
return (
<>
<div>
<h4>content from child</h4>
<Component
content={content}
ReplaceButton={ReplaceButton}
{...rest as P}
/>
<hr/>
</div>
<div>
<h4>content from wrapper</h4>
<Hello
content={content}
ReplaceButton={ReplaceButton}
/>
<hr/>
</div>
</>
);
Hello component is straightforward
<div>
<p>{content}</p>
<div>
{ReplaceButton}
</div>
</div>
and that's how wrapped is being made
const HelloWithReplacement = withReplacement(Hello);
But the problem is that content is being replaced only in 2nd part. 1st remains untouched.
In the main App component I also replace the content after 20 sec from loading.
const [ content, setContent ] = useState( 'original content');
useEffect(() => {
setTimeout(() => {
setContent('...too late! replaced from main component');
}, 10000);
}, []);
...when I call my wrapped component like this
return (
<div className="App">
<HelloWithReplacement
content={content}
getContent={() => content}
/>
</div>
);
And it also has the issue - 1st part is updating, 2nd part does not.
It looks like you are overriding the withReplacement internal state with the external state of the App
<HelloWithReplacement
content={content} // Remove this to stop overriding it
getContent={() => content}
/>
Anyway it looks weird to use two different states, it is better to manage your app state in only one place

Jest: functional component array list not mapping after adding element

I am trying to list person by clicking onClick function to add empty object so that map can loop
over and show div. I see prop onclick function calling works but i map is not looping over it.
a little help would be appreciated.
// functional component
const listing = ({list=[]}) => (
<>
{
<div id='addPerson' onClick={() => list.push({})}>Add person</div>}
{list.map((element, index) => (
<div key={index} className='listItems'>
<p>list name</p>
</div>
))}
</>
);
export default listing;
// test case
it('renders list-items', () => {
const wrapper = shallow(<listing />);
wrapper.find('#addPerson').prop('onClick')();
console.log(wrapper.find('.listItems').length); // showing zero, expected 1
});
Updating List is not going to cause a re-render in your component. I am not sure where List is coming from, but I am assuming it is coming from a local state in the parent, or probably redux store. From inside your component, you will have to call a function in your parent to update the list array there.
Below is a version of your component, assuming the list in your local state. You will have to adjust the code to update the state in the parent or redux store, but the below should be enough to give you an idea of what to do. Also, don't push the element to array, as it mutates the object and will not contact re-render.
const Listing = ({list = [], addToList}) => {
return <>
{
<div id='addPerson' onClick={() => addToList({})}>Add person</div>}
{list.map((element, index) => (
<div key={index} className='listItems'>
<p>list name</p>
</div>
))}
</>
};

How to show a block of collapsible text on click of button

I am trying to implement a collapsible component. I have designed it such as, on click of a button, a block of dynamic text will appear. I made a functional component and using the tags in a class. The name of the component is, CustomAccordion.jsx and using this component in Container.jsx
I have tried to create a button and a function for onClick event.
Part of the CustonAccordion.jsx
const handleToggle = () : string =>{
let content = this.nextElementSibling;
if (content.style.maxHeight){
content.style.maxHeight = null;
}else{
content.style.maxHeight = content.scrollHeight +'px';
}
}
export default function CustomAccordion(props: PropType): React.Component<*> {
const { title, children } = props
return(
<div>
<AccordionButton onClick={() => this.handleToggle()}>{title}</AccordionButton>
<AccordionContent>
<p>{children}
</p>
</AccordionContent>
</div>
)
}
Part of calling Container.jsx
<CustomAccordion title = {this.props.name}>
<p>This is the text passed to component.</p>
</CustomAccordion>
<br />
This does not show the expanded text and it seems that the click event does not work properly. I am very new in react, guessing the syntax might be incorrect.
In react you should generally try to avoid touching DOM directly unless you really have to.
Also you are accessing the handleToggle function wrongly. It should be onClick={() => handleToggle()} because this in your case is window/null and so it has no handleToggle method.
Instead you can use a stateful class component to achieve the same thing.
export default class CustomAccordion extends React.Component {
state = {show: false};
toggle = () => this.setState({show: !this.state.show});
render() {
const {title, children} = this.props;
const {show} = this.state;
return (
<div>
<AccordionButton onClick={this.toggle}>{title}</AccordionButton>
{show && (
<AccordionContent>
<p>{children}</p>
</AccordionContent>
)}
</div>
)
}
}
If you want to have some kind of animation, you can set different className based on the show state instead of adding/removing the elements.

Resources